#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <cmath>
#include <sstream>

#include "Perfs.h"
#include "Typedefs.h"
#include "Utils.h"
#include "Kadane.h"
#include "Bae.h"
#include "Data.h"
#include "DivideAndConquer.h"
#include "Result.h"
#include "CentralRowSolver.h"
#include "CentralColumnSolver.h"

#ifdef _OPENMP
#include <omp.h>
#endif

/* Check program arguments */
vector<string> CheckOpt(int argc, char **argv, int &num_threads, int &num_inputs)
{
	vector<string> FileName;

	if(argc == 2)
	{
		if (!string("-h").compare(argv[1]) || !string("-help").compare(argv[1]))
		{
			cout << usage << endl;
		}
		else
                {
			#if DISPLAY
                        cout << "Taking program parameter as a filename" << endl;
                        #endif
			FileName.push_back(argv[1]);
			num_inputs = 1;
                }
	}

	if (argc > 2)
        {
		num_threads= atoi(argv[1]);

		string strFile;

		for (int i = 2; i < argc; i ++)
		{
			stringstream ss;
			ss << argv[i];
			//cout << "i =" << i << " => " << argv[i];
			ss >> strFile;
			//cout << " => " << strFile << endl;
			//strFile = argv[i];
			FileName.push_back(strFile);
		}
		num_inputs = argc - 2;
        }

	return FileName;
}

void handle_omp_parallelism(int num_threads)
{
    //omp_set_nested(true);	// Nesting parallelism enabled
    omp_set_num_threads(num_threads);
    #pragma omp parallel
    {
            #pragma omp master
            {
            #if DISPLAY
            cout << omp_get_num_threads() << " threads used on " << omp_get_num_procs() << " available procs.";
            cout  << " Nested parallelism is " << (omp_get_nested() == 1 ? "ON." : "OFF.") << endl;
            #endif
            }
    }
}



int main(int argc, char **argv)
{
	double fTimeStart, fTimeEnd;
	int num_threads;
	int num_inputs = 0;
	//string FileName = "Input.txt";
	vector<string> FileName;
	int divide_and_conquer_recursivity = 0;

#ifdef _OPENMP
	num_threads = omp_get_num_procs();	// Maximum available cores used by default
	num_threads = omp_get_max_threads();	// Maximum available threads used by default
	divide_and_conquer_recursivity = log2(num_threads);
#else
	num_threads = 1;			// Monocore
#endif

	FileName = CheckOpt(argc, argv, num_threads, num_inputs);
	//cout << FileName[0] << FileName.size() << endl;


        handle_omp_parallelism(num_threads);

	/* Loop for several inputs */
	for (int i = 0; i < num_inputs; i ++)
	{
		// CAS 2D
		/* DATA LOADING */
		fTimeStart = Perfs::my_gettimeofday();
		Data Input(FileName[i]);
		fTimeEnd = Perfs::my_gettimeofday();

		#if DISPLAY
		double fTimeDataLoad = fTimeEnd - fTimeStart;

		cout << FileName[i] << " : " << Input.getM() << "x" << Input.getN() << endl;

		// Print matrix only if it has few elements...
		if (Input.getM() < 10)
		{
			Input.dispMat();
			//cout << endl;
			//Input.dispVect();
		}

		cout << "Data load: " << fTimeDataLoad << " s" <<endl;
		#endif


	/* TO CHANGE OPTIONS CHECK inc/Utils.h */


/* KADANE OPTIMIZED */
#if KADANE_OPTIMIZED
	#if DISPLAY_TIME
		fTimeStart = Perfs::my_gettimeofday();
	#endif
		vector<Result> vResults;
		Kadane::kadane_optimized(Input, vResults);
	#if DISPLAY_TIME
		fTimeEnd = Perfs::my_gettimeofday();
		double fTimeKadaneFinal = fTimeEnd - fTimeStart;
	#endif
		for (t_uint i = 0; i < vResults.size(); i ++)
			vResults[i].display();
	#if DISPLAY_TIME
		cout << "KadaneOpti: " << fTimeKadaneFinal << " s" << endl;
	#endif
#endif

		/* KADANE SIMPLE */
#if KADANE_2D
	#if DISPLAY_TIME
		fTimeStart = Perfs::my_gettimeofday();
	#endif
		Result result;
		Kadane::kadane2D(Input, result, num_threads);
	#if DISPLAY_TIME
		fTimeEnd = Perfs::my_gettimeofday();
		double fTimeKadane = fTimeEnd - fTimeStart;
	#endif
		result.display();
	#if DISPLAY_TIME
		cout << "Kadane: " << fTimeKadane << " s" << endl;
	#endif
#endif




		/* KADANE SIMPLE WITH VECT STORAGE */
#if KADANE_VECT
		fTimeStart = Perfs::my_gettimeofday();
		Result result2;
		Kadane::kadane2DVect(Input, result2);
		fTimeEnd = Perfs::my_gettimeofday();
		double fTimeKadaneVect = fTimeEnd - fTimeStart;
		result2.display();
		cout << "KadaneVect: " << fTimeKadaneVect << " s" << endl;

#endif
		/* DIVIDE AND CONQUER */
#if DIVIDE_CONQUER
		Result result11;
		DivideAndConquer divide_and_conquer(divide_and_conquer_recursivity);
		fTimeStart = Perfs::my_gettimeofday();
		divide_and_conquer.solve_MCS(Input, result11);
		fTimeEnd = Perfs::my_gettimeofday();
		double fTimeDivideAndConquer = fTimeEnd - fTimeStart;
		result11.display();
		cout << "DC: " << fTimeDivideAndConquer << " s" << endl;
#endif

		/* KADANE BLOCK */
#if KADANE_BLOCK
	       // cout << "Kadane Block" << endl;
		Result result3;
		s_rect rSubBlock; rSubBlock.x0 = 0; rSubBlock.x1 = Input.getColumns() - 1;
		rSubBlock.y0 = 0; rSubBlock.y1 = Input.getRows() - 1;
		//cout << rSubBlock.x1 << " | " << rSubBlock.y1 << endl;
		fTimeStart = Perfs::my_gettimeofday();
		Kadane::kadane2DBlock(Input, result3, rSubBlock);
		fTimeEnd = Perfs::my_gettimeofday();
		double fTimekadane2DBlock = fTimeEnd - fTimeStart;
		result3.display();
		cout << "Kadane2DBlock: " << fTimekadane2DBlock << " s" << endl;
#endif

		/* BAE ALGO WITH PREFIX SUM */
#if BAE
		Result result_bae;
		fTimeStart = Perfs::my_gettimeofday();
		Bae bae(Input);
		bae.bae_algo(result_bae);
		fTimeEnd = Perfs::my_gettimeofday();
		double fTimeBae = fTimeEnd - fTimeStart;
		result_bae.display();
		cout << "Bae: " << fTimeBae << " s" << endl;
#endif
	}

	return EXIT_SUCCESS;
}
